#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "dequeue.h"
#include "client_server_lib.h"

#define NUM_CLIENTS 2     //max number of clients we intend to serve
#define MAX_MSG_SIZE 256

void *serve_client(void *args);

struct thread_args{
	int fd;
	dequeue *dq;
};

int main(int argc, char **argv)
{

	int listener;     // listening socket descriptor
	int newfd;        // newly accept()ed socket descriptor
	char *PORT;			/*DEBUG we might need to check input - this var may be < 0*/
	struct sockaddr_in remoteaddr; // client address
	socklen_t addrlen;

	int yes=1;        // for setsockopt() SO_REUSEADDR, below
	int rv;

	struct addrinfo hints, *ai;

	pthread_t thread;
	int thread_rv;
	int fdArr[1];
	struct thread_args ta[1];

	dequeue *dq;

	if (argc != 2){
		fprintf(stdout, "ERROR: please enter port number as an argument\n");
		return 0;
	}
	PORT = argv[1];

	//create the dequeue
	dq = createDequeue();

	// get us a socket and bind it

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;
	if ((rv = getaddrinfo("127.0.0.1", PORT, &hints, &ai)) != 0) {
		fprintf(stdout, "ERROR: %s\n", gai_strerror(rv));   //rv is just for error ref.
		return 0;
	}

	listener = socket(ai->ai_family, ai->ai_socktype, 0);
	if (listener < 0) {
		fprintf(stdout, "ERROR: couldn't get a socket from the OS!!\n");
		freeaddrinfo(ai); // all done with this
		return 0;
	}
	fprintf(stdout,"fd = %d\n", listener);				/*DEBUG*/

	// lose the pesky "address already in use" error message   --- this is another one of beej's functions
	setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

	if (bind(listener, ai->ai_addr, ai->ai_addrlen) < 0) {
		close(listener);
		freeaddrinfo(ai); // all done with this
		return 0;
	}

	freeaddrinfo(ai); // all done with this

	// listen
	if (listen(listener, 15) == -1) {
		perror("listen");
		close(listener);
		return 0;
	}

	ta[0].ExitFlag = &ExitFlag;
	ta[0].dq = dq;

	// main loop
	while(1) {
		newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
		fprintf(stdout, "new fd is: %d\n", newfd);
		ta[0].fd = newfd;
		thread_rv = pthread_create(&thread, NULL, (void *)serve_client, (void *)&ta);
		if (thread_rv){
			fprintf(stderr, "ERROR; return code from pthread_create() is %d\n", thread_rv);
			/*DEBUG: add here every thing we need to close before we kill the program*/
			ExitFlag = 1; // signal the threads to wrap things up
		}
	}

	freeDequeue(dq);
	close(listener);
	return 0;
}


/*the thread's function to handle a single client on stackServer prog.*/
void *serve_client(void *args){
	int numbytes = -2, fd;
	char buf[MAX_MSG_SIZE], dqBuffer[MAX_MSG_SIZE];
	struct thread_args *ta;
	dequeue *dq;

	ta = (struct thread_args *)args;

	fd = ta->fd;
	dq = ta->dq;

	while (1){
		numbytes = recv(fd, buf, MAX_MSG_SIZE ,0);
		fprintf(stdout, "DEBUG: %d\n", numbytes);     /*DEBUG*/
		if (numbytes == 0){    //connection closed
			break;
		}

		if (numbytes == -1){
			fprintf(stdout, "error handling client\n");
			break;
		}

		buf[numbytes] = '\0';
		fprintf(stdout, "buf in thread is == %s\n", buf);				/*DEBUG*/
		if (strcmp(buf, "exit") == 0){
			break;
		}
		else if ((pushCommand(buf)) == 1){
			extractPushStr(dqBuffer, buf);
			if (push(dq, dqBuffer) == 0){
				fprintf(stderr, "ERROR: couldn't push to the dequeue\n");
				continue;
			}
		}
		else if (strcmp(buf, "POP") == 0){
			if (pop(dq) == 0){
				fprintf(stderr, "ERROR: couldn't pop() the dequeue\n");
				continue;
			}
		}
		else if (strcmp(buf, "TOP") == 0){
			if (top(dq, buf, sizeof(buf)) == 0){
				fprintf(stderr, "ERROR: couldn't top() the dequeue\n");
				continue;
			}

			numbytes += 7;
			if (sendall(fd, strcat("OUTPUT: ", buf), &numbytes) == -1){
				fprintf(stderr, "ERROR: failed to send the whole buffer\n"
						"sent only %d bytes out of %d, \n", numbytes, (int)(strlen(buf)+7));
			}
		}
		/*TODO:
 		 *  add here the dequeue cmds cases
 		 */

	}

	close(fd);
	pthread_exit(NULL);
}




